﻿using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using RazorEngine;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using RazorEngine.Templating;
using System.Security.Policy;
using System.Security;
using System.Reflection;
using System.Security.Permissions;
using RazorEngine.Configuration;
using RazorEngine.Text;

namespace AbpHelper
{
    public class SwaggerModelPoco
    {
        public string name { get; set; }
        public string tags { get; set; }
        public List<SwaggerObjectPocoProperty> fields { get; set; }
    }

    class Program
    {
        private static AppDomain appDomain;

        static void Main(string[] args)
        {
            //CreateOwnerDomain();

            InitRazor();

            var jsonString = File.ReadAllText("swagger.json");

            var json = JsonConvert.DeserializeObject<SwaggerPoco>(jsonString);

            var models = new List<SwaggerModelPoco>();
            foreach (KeyValuePair<string, JToken> kv in json.definitions)
            {
                if (kv.Key.IndexOf(']')>0)
                {
                    continue;
                }
                //Console.WriteLine(kv.Key);
                var poco = kv.Value.ToObject<SwaggerObjectPoco>();
                var dict = new List<SwaggerObjectPocoProperty>();
                foreach (JProperty property in poco.properties.Children())
                {
                    var perty = property.Value.ToObject<SwaggerObjectPocoProperty>();

                    if (perty != null)
                    {
                        perty.name = property.Name;
                        if (poco.required != null && poco.required.Contains(perty.name))
                        {
                            perty.requird = true;
                        }

                        if (perty.schema != null)
                        {
                            var sref = perty.schema["$ref"];
                            if (sref == null)
                            {
                                sref = perty.schema["items"]["$ref"];
                            }
                            var outputRef = sref.ToString();
                            var outputSchema = GetSchema(outputRef);
                            perty.properyType = outputSchema;
                        }
                        dict.Add(perty);
                    }
                    else
                    {
                        var rref = property.Value.Value<string>("$ref");
                        var required = property.Value.Value<bool>("required");
                        var schema = GetSchema(rref);
                        perty = new SwaggerObjectPocoProperty()
                        {
                            name = property.Name,
                            requird = required,
                            properyType = schema
                        };
                        dict.Add(perty);
                    }
                }
                models.Add(new SwaggerModelPoco()
                {
                    name = kv.Key,
                    fields = dict
                });
            }

            var servers = new List<SwaggerServicePoco>();
            foreach (KeyValuePair<string, JToken> kv in json.paths)
            {
                //Console.WriteLine(kv.Key);
                foreach (JProperty method in kv.Value.Children())
                {
                    var body = method.Value.ToObject<SwaggerParamBody>();
                    var status = body.responses["200"].ToObject<SwaggerParameterResponseStatus>();
                    var servicePoco = new SwaggerServicePoco()
                    {
                        operationId = body.operationId,
                        path = kv.Key,
                        method = method.Name,
                        tags = body.tags.FirstOrDefault(),
                        body = body,
                        //models = new Dictionary<string, List<SwaggerObjectPocoProperty>>()
                    };
                    //var outputRef = string.Empty;
                    //var inputRef = string.Empty;
                    if (status.schema != null)
                    {
                        var sref = status.schema["$ref"];
                        if (sref == null)
                        {
                            sref = status.schema["items"]["$ref"];
                        }
                        var outputRef = sref.ToString();
                        var outputSchema = GetSchema(outputRef);
                        var model = models.FirstOrDefault(o => o.name == outputSchema);
                        if (model != null)
                        {
                            model.tags = body.tags.First();
                        }
                    }

                    if (body.parameters != null && body.parameters.Count > 0)
                    {
                        var list = new List<SwaggerObjectPocoProperty>();
                        foreach (var item in body.parameters)
                        {
                            if (item.schema != null)
                            {
                                var rref = item.schema.Value<string>("$ref");
                                if (!string.IsNullOrEmpty(rref))
                                {
                                    var schema = GetSchema(rref);
                                    var model = models.FirstOrDefault(o => o.name == schema);
                                    if (model != null)
                                    {
                                        model.tags = body.tags.First();
                                    }
                                }
                            }

                            var poco = new SwaggerObjectPocoProperty();
                            poco.requird = item.required;
                            poco.name = item.name;
                            poco.properyType = item.properyType;
                            list.Add(poco);
                        }
                        var inputRef = method.Path.Split('.')[0];
                        var inputSchema = GetSchema(inputRef) + "Input";
                        if (!Exclude(inputRef) && list.Count > 0)
                        {
                            if (!models.Any(o => o.name == inputSchema))
                            {
                                var poco = new SwaggerModelPoco()
                                {
                                    name = inputSchema,
                                    fields = list,
                                    tags = body.tags.First()
                                };
                                models.Add(poco);
                            }
                        }
                    }

                    servers.Add(servicePoco);
                }
            }

            FormatModel(models);

            GenerateCode("GEN", servers, models);

            Console.WriteLine("generate code finish!");

            //var exitCode = appDomain.ExecuteAssembly(Assembly.GetExecutingAssembly().Location);
            //AppDomain.Unload(appDomain);
            //Console.ReadKey();
        }

        private static bool Exclude(string path)
        {
            return path.IndexOf("NameValue") > -1 || path.IndexOf("EntityDto") > -1;
        }

        /// <summary>
        /// 初始化模板引擎
        /// </summary>
        private static void InitRazor()
        {
            var config = new TemplateServiceConfiguration();
            var service = RazorEngineService.Create(config);
            config.EncodedStringFactory = new RawStringFactory();
            config.DisableTempFileLocking = true;
            config.Debug = true;
            Engine.Razor = service;
        }

        /// <summary>
        /// 格式化输出的模型
        /// </summary>
        /// <param name="models"></param>
        private static void FormatModel(List<SwaggerModelPoco> models)
        {
            foreach (var item in models)
            {
                if (string.IsNullOrEmpty(item.tags))
                {
                    var model = models.FirstOrDefault(o => o.fields.Any(p => p.properyType == item.name));
                    if (model != null)
                    {
                        item.tags = model.tags;
                    }
                }

                foreach (var perty in item.fields)
                {
                    if (!string.IsNullOrEmpty(perty.format))
                    {
                        perty.properyType = perty.format;
                    }

                    if (string.IsNullOrEmpty(perty.properyType))
                    {
                        perty.properyType = "string";
                    }

                    switch (perty.properyType)
                    {
                        case "date-time":
                            perty.properyType = "DateTime";
                            break;
                        case "int32":
                            perty.properyType = "int";
                            break;
                        case "int64":
                            perty.properyType = "long";
                            break;
                        case "integer":
                            perty.properyType = "int";
                            break;
                        case "boolean":
                            perty.properyType = "bool";
                            break;
                        case "uuid":
                            perty.properyType = "Guid";
                            break;
                        case "array":
                            perty.properyType = "Array";
                            if (perty.items != null && perty.items.Count > 0)
                            {
                                var rref = perty.items.Value<string>("$ref");
                                var required = perty.items.Value<bool>("required");
                                if (!string.IsNullOrEmpty(rref))
                                {
                                    var schema = GetSchema(rref);
                                    perty.requird = required;
                                    perty.properyType = schema;
                                }
                            }
                            break;
                    }
                }
            }
        }

        private static string GetSchema(string rref)
        {
            rref = rref.Substring(rref.LastIndexOf('/') + 1);
            var pos = rref.IndexOf('[');
            if (pos > 0)
            {
                rref = rref.Substring(pos + 1, rref.LastIndexOf(']') - pos - 1);
            }
            return rref;
        }

        private static void CreateOwnerDomain()
        {
            if (AppDomain.CurrentDomain.IsDefaultAppDomain())
            {
                // RazorEngine cannot clean up from the default appdomain...
                Console.WriteLine("Switching to secound AppDomain, for RazorEngine...");
                AppDomainSetup adSetup = new AppDomainSetup();
                adSetup.ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
                var current = AppDomain.CurrentDomain;
                // You only need to add strongnames when your appdomain is not a full trust environment.
                var strongNames = new StrongName[0];

                appDomain = AppDomain.CreateDomain(
                    "MyMainDomain", null,
                    current.SetupInformation, new PermissionSet(PermissionState.Unrestricted),
                    strongNames);
                Console.WriteLine("Create AppDomain finish...");
            }
        }

        private static void GenerateCode(string root, List<SwaggerServicePoco> servers, List<SwaggerModelPoco> models)
        {
            var serverGroup = servers.GroupBy(o => o.tags);
            var modelGroup = models.GroupBy(o => o.tags);
            foreach (var item in serverGroup)
            {
                var dir = Path.Combine(root, item.Key);
                if (Directory.Exists(dir))
                {
                    Directory.Delete(dir, true);
                }

                Directory.CreateDirectory(dir);

                var svr = new ServerPoto();
                svr.tags = item.Key;
                svr.items = new List<Tuple<string, string>>();

                foreach (var service in item)
                {
                    svr.items.Add(new Tuple<string, string>(service.operationId, service.path));
                }

                var fileName = string.Format("{0}\\{1}Service.cs", Path.Combine(root, item.Key), item.Key);
                WriteGenerateCode("Service.rz", fileName, item.Key, svr);
            }

            var schemaList = new List<string>();
            foreach (var item in modelGroup)
            {
                foreach (var model in item)
                {
                    schemaList.Add(model.name);
                    if (!string.IsNullOrEmpty(item.Key))
                    {
                        var modelFileName = string.Format("{0}\\{1}.cs", Path.Combine(root, item.Key), model.name);
                        WriteGenerateCode("Model.cshtml", modelFileName, model.name, model);
                    }
                }
            }
        }

        private static void WriteGenerateCode<T>(string templatePath, string fileName, string key, T model)
        {
            var svrcontent = GetContent(templatePath, key, model);
            if (!string.IsNullOrEmpty(svrcontent))
            {
                using (FileStream fs = new FileStream(fileName, FileMode.Create))
                {
                    //获得字节数组
                    byte[] data = System.Text.Encoding.Default.GetBytes(svrcontent);
                    //开始写入
                    fs.Write(data, 0, data.Length);
                    //清空缓冲区、关闭流
                    fs.Flush();
                    fs.Close();
                }
            }
        }

        private static string GetContent<T>(string templatePath, string templateKey, T model)
        {
            string result = string.Empty;
            if (!File.Exists(templatePath))
            {
                return null;
            }

            string template = File.ReadAllText(templatePath);
            result = Engine.Razor.RunCompile(template, templateKey, typeof(T), model);
            return result;
        }
    }
}
